﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Gmmy.DomainExtension;
using Microsoft.EntityFrameworkCore;
using Surging.Core.CPlatform.Ioc;

namespace Gmmy.RepositoryExtension
{
    public abstract class RepositoryFramework<T> : ServiceBase, IRepository<T, EntityList<T>> 
        where T : EntityBase, new()
    {
        
        protected BaseDbContext DataContext;
        public object UnitOfWork { get; set; }

        protected readonly DbSet<T> dbset;

        protected RepositoryFramework(IDataBaseFactory<BaseDbContext> factory)
        {
            DataContext = factory.Get();
            dbset = DataContext.Set<T>();
            UnitOfWork = DataContext;
        }
        #region 异步 IRepository<T,PageData<T>> 成员

        public virtual async Task<EntityList<T>> FindAllAsync<S>(int PageIndex, int PageSize, ISpecification<T> specification, System.Linq.Expressions.Expression<Func<T, S>> orderByExpression, bool IsDESC, bool? AsNoTracking = true, List<SortParms<T>> thenByExpression = null)
        {
            var query = IsDESC
                     ?
                     dbset.Where(specification.SatisfiedBy()).OrderByDescending(orderByExpression)
                     :
                     dbset.Where(specification.SatisfiedBy()).OrderBy(orderByExpression);
            query = DataSort(query, thenByExpression);
            var pageData = new EntityList<T>();
            pageData.Data = new List<T>();
            var quyCount = await query.CountAsync();
            if (quyCount > 0)
            {
                if (PageSize > 0)
                {
                    pageData.Total = quyCount;
                    int TotalPages = (int) Math.Ceiling(pageData.Total / (double) PageSize);
                    pageData.Page = PageIndex < 1 ? 1 : PageIndex;
                    pageData.TotalPage = TotalPages;
                    if (AsNoTracking == true)
                        pageData.Data = await query.Skip((pageData.Page - 1) * PageSize).Take(PageSize).AsNoTracking()
                            .ToListAsync();
                    else
                        pageData.Data = await query.Skip((pageData.Page - 1) * PageSize).Take(PageSize).ToListAsync();
                }
                else
                {
                    pageData.Data = new List<T>();
                }
            }
            return pageData;
        }
        public virtual async Task<EntityList<T>> FindAllAsync<S>(int PageIndex, int PageSize, ISpecification<T> specification, Dictionary<string, string> orderByExpression, bool? AsNoTracking = true, List<SortParms<T>> thenByExpression = null)
        {
            var query = dbset.Where(specification.SatisfiedBy());
            query = DataSort(query, orderByExpression);
            query = DataSort(query, thenByExpression);
            var pageData = new EntityList<T>();
            pageData.Data = new List<T>();
            int quyCount = await query.CountAsync();
            if (quyCount > 0)
            {
                pageData.Total = quyCount;
                int totalPages = (int)Math.Ceiling(pageData.Total / (double)PageSize);
                pageData.Page = PageIndex < 1 ? 1 : PageIndex;
                pageData.TotalPage = totalPages;
                if (AsNoTracking == true)
                    pageData.Data = await query.Skip((pageData.Page - 1) * PageSize).Take(PageSize).AsNoTracking().ToListAsync();
                else
                    pageData.Data = await query.Skip((pageData.Page - 1) * PageSize).Take(PageSize).ToListAsync();
            }
            return pageData;
        }
        public virtual async Task<IEnumerable<T>> GetAllAsync(bool? AsNoTracking = true)
        {
            if (AsNoTracking == true) return await dbset.AsNoTracking().ToListAsync();
            return await dbset.ToListAsync();
        }

        public virtual async Task<IEnumerable<T>> GetManyAsync(ISpecification<T> specification, bool? AsNoTracking = true)
        {
            if (AsNoTracking == true) return await dbset.Where(specification.SatisfiedBy()).AsNoTracking().ToListAsync();
            return await dbset.Where(specification.SatisfiedBy()).ToListAsync();
        }

        public virtual async Task<IEnumerable<T>> GetListByTopNAsync<S>(int TopN, ISpecification<T> specification, System.Linq.Expressions.Expression<Func<T, S>> orderByExpression, bool IsDESC, bool? AsNoTracking = true, List<SortParms<T>> thenByExpression = null)
        {
            var query = IsDESC
                 ?
                 dbset.Where(specification.SatisfiedBy()).OrderByDescending(orderByExpression)
                 :
                 dbset.Where(specification.SatisfiedBy()).OrderBy(orderByExpression);
            query = DataSort(query, thenByExpression);
            if (AsNoTracking == true) return await query.Take(TopN).AsNoTracking().ToListAsync();
            return await query.Take(TopN).ToListAsync();
        }

        public virtual async Task<T> GetByConditionAsync(ISpecification<T> specification, bool? AsNoTracking = false)
        {
            if (AsNoTracking == false)
                return await dbset.Where(specification.SatisfiedBy()).FirstOrDefaultAsync<T>();
            else return await dbset.Where(specification.SatisfiedBy()).AsNoTracking().FirstOrDefaultAsync<T>();
        }

        public virtual async Task<T> GetByKeyAsync(object key)
        {
            return await dbset.FindAsync(key);
        }

        public virtual void Add(T entity)
        {
            dbset.Add(entity);
        }

        public virtual void AddBatch(IEnumerable<T> entities)
        {
            //dbset.AddRange(entities);
            DataContext.AddRange(entities);
        }

        public virtual void Modify(T entity)
        {
            DataContext.Entry(entity).State = EntityState.Modified;
        }

        public void Update(T entity, params Expression<Func<T, object>>[] properties)
        {
            DataContext.AttachUpdated(entity);
            var entry = DataContext.Entry(entity);
            foreach (var selector in properties)
            { entry.Property(selector).IsModified = true; }
        }
        public void LogicDelete(ISpecification<T> specification)
        {
            IEnumerable<T> objects = dbset.Where<T>(specification.SatisfiedBy()).AsEnumerable();
            foreach (dynamic obj in objects)
            {
                obj.IsDeleted = true;
            }
        }
        public virtual void Remove(T entity)
        {
            dbset.Remove(entity);
        }

        public virtual void Remove(ISpecification<T> specification)
        {
            IEnumerable<T> objects = dbset.Where<T>(specification.SatisfiedBy()).AsEnumerable();
            foreach (T obj in objects)
                dbset.Remove(obj);
        }
        public virtual async Task<bool> ExistsAsync(ISpecification<T> specification)
        {
            return await dbset.AnyAsync(specification.SatisfiedBy());
        }
        //跟踪实体
        public virtual IEnumerable<T> GetWithRawSql(string query, params object[] parameters)
        { return dbset.FromSql(query, parameters); }

        public async Task<int> GetCountAsync(bool configureAwait = true)
        {
            return await dbset.CountAsync();
        }
        public async Task<int> GetCountAsync(ISpecification<T> specification)
        {
            return await dbset.Where(specification.SatisfiedBy()).CountAsync();
        }

        public virtual async Task<EntityList<M>> FindAllAsyncAsQuery<M, S>(IQueryable<M> query, int PageIndex, int PageSize,
            Expression<Func<M, S>> orderByExpression, bool IsDESC, bool? AsNoTracking = true, List<SortParms<M>> thenByExpression = null) where M : class, new()
        {
            EntityList<M> pageData = new EntityList<M>();
            pageData.Data = new List<M>();
            int quyCount = await query.CountAsync();
            if (quyCount > 0)
            {
                pageData.Total = quyCount;
                int TotalPages = (int)Math.Ceiling(pageData.Total / (double)PageSize);
                pageData.TotalPage = TotalPages;
                pageData.Page = PageIndex < 1 ? 1 : PageIndex;
                if (IsDESC)
                {
                    query = query.OrderByDescending(orderByExpression);
                    query = DataSort(query, thenByExpression);
                }
                else
                {
                    query = query.OrderBy(orderByExpression);
                    query = DataSort(query, thenByExpression);
                }
                pageData.Data = AsNoTracking == true ? await query.Skip((pageData.Page - 1) * PageSize).Take(PageSize).AsNoTracking().ToListAsync() : await query.Skip((pageData.Page - 1) * PageSize).Take(PageSize).ToListAsync();
            }
            return pageData;
        }
        public static IOrderedQueryable<M> DataSort<M>(IQueryable<M> source, List<SortParms<M>> thenByExpression) where M : class, new()
        {
            if (thenByExpression == null)
                return (IOrderedQueryable<M>)source;
            var type = typeof(M);
            thenByExpression.ForEach(x =>
            {
                var sortingDir = !x.ISDESC ? "ThenBy" : "ThenByDescending";
                var property = GetPropertyInfo(x.Func);
                var param = Expression.Parameter(type, property.Name);
                var pi = type.GetProperty(property.Name);
                Type[] types = new Type[2];
                types[0] = type;
                types[1] = pi.PropertyType;
                var expr = Expression.Call(typeof(Queryable), sortingDir, types, source.Expression, Expression.Lambda(Expression.Property(param, property.Name), param));
                source = source.AsQueryable().Provider.CreateQuery<M>(expr);
            });
            return (IOrderedQueryable<M>)source;
        }

        public virtual async Task<EntityList<M>> FindAllAsyncAsQuery<M>(IQueryable<M> query, int PageIndex, int PageSize, bool? AsNoTracking = true, List<SortParms<M>> thenByExpression = null) where M : class, new()
        {
            EntityList<M> pageData = new EntityList<M>();
            pageData.Data = new List<M>();
            int quyCount = await query.CountAsync();
            if (quyCount > 0)
            {
                pageData.Total = quyCount;
                int TotalPages = (int)Math.Ceiling(pageData.Total / (double)PageSize);
                pageData.TotalPage = TotalPages;
                pageData.Page = PageIndex < 1 ? 1 : PageIndex;
                pageData.Data = AsNoTracking == true ? await query.Skip((pageData.Page - 1) * PageSize).Take(PageSize).AsNoTracking().ToListAsync() : await query.Skip((pageData.Page - 1) * PageSize).Take(PageSize).ToListAsync();
            }
            return pageData;
        }
        public static IOrderedQueryable<M> DataSort<M>(IQueryable<M> source, Dictionary<string, string> sort) where M : class, new()
        {
            if (!sort.Any())
                return (IOrderedQueryable<M>)source;
            var type = typeof(M);
            var sortingDir = sort.Values.FirstOrDefault() == "ASC" ? "OrderBy" : "OrderByDescending";
            var property = type.GetProperties().FirstOrDefault(x => x.Name == sort.Keys.FirstOrDefault());
            var param = Expression.Parameter(type, property.Name);
            var pi = type.GetProperty(property.Name);
            Type[] types = new Type[2];
            types[0] = type;
            types[1] = pi.PropertyType;
            var expr = Expression.Call(typeof(Queryable), sortingDir, types, source.Expression, Expression.Lambda(Expression.Property(param, property.Name), param));
            source = source.AsQueryable().Provider.CreateQuery<M>(expr);
            if (sort.Count > 1)
            {
                var index = 0;
                sort.ToList().ForEach(x =>
                {
                    if (index != 0)
                    {
                        sortingDir = x.Value == "ASC" ? "ThenBy" : "ThenByDescending";
                        property = type.GetProperties().FirstOrDefault(y => y.Name == x.Key);
                        param = Expression.Parameter(type, property.Name);
                        pi = type.GetProperty(property.Name);
                        types = new Type[2];
                        types[0] = type;
                        types[1] = pi.PropertyType;
                        expr = Expression.Call(typeof(Queryable), sortingDir, types, source.Expression,
                            Expression.Lambda(Expression.Property(param, property.Name), param));
                        source = source.AsQueryable().Provider.CreateQuery<M>(expr);
                    }
                    index++;
                });
            }
            return (IOrderedQueryable<M>)source;
        }
        public static PropertyInfo GetPropertyInfo<M>(Expression<Func<M, dynamic>> select)
        {
            var body = select.Body;
            if (body.NodeType == ExpressionType.Convert)
            {
                var o = ((UnaryExpression)body).Operand;
                return ((MemberExpression)o).Member as PropertyInfo;
            }
            else if (body.NodeType == ExpressionType.MemberAccess)
            {
                return ((MemberExpression)body).Member as PropertyInfo;
            }
            return null;
        }
        #endregion
    }
}
